package com.mlethe.library.calendarview;

import android.content.Context;

import androidx.annotation.NonNull;

import java.util.ArrayList;
import java.util.List;

/**
 * 一些日期辅助计算工具
 */
final class CalendarUtil {

    /**
     * 为月视图初始化日历
     *
     * @param year          year
     * @param month         month
     * @param monthStartDay 月起始日
     * @param delegate     delegate
     * @return 为月视图初始化日历项
     */
    @NonNull
    static List<Calendar> initCalendarForMonthView(int year, int month, int monthStartDay, CalendarViewDelegate delegate) {
        java.util.Calendar calendar = java.util.Calendar.getInstance();
        calendar.clear();
        calendar.set(java.util.Calendar.YEAR, year);
        calendar.set(java.util.Calendar.DAY_OF_MONTH, 1);
        calendar.set(java.util.Calendar.MONTH, month - 1);
        int actualMaximum = calendar.getActualMaximum(java.util.Calendar.DATE);
        calendar.set(java.util.Calendar.DAY_OF_MONTH, Math.min(monthStartDay, actualMaximum));
        calendar.set(java.util.Calendar.HOUR_OF_DAY, 0);
        calendar.set(java.util.Calendar.MINUTE, 0);
        calendar.set(java.util.Calendar.SECOND, 0);
        calendar.set(java.util.Calendar.MILLISECOND, 0);
        int week = calendar.get(java.util.Calendar.DAY_OF_WEEK);
        int preDiff = week == 7 ? 0 : week;
        int weekStart = delegate.getWeekStart();
        if (weekStart == CalendarViewDelegate.WEEK_START_WITH_SUN) {
            preDiff = week - 1;
        } else if (weekStart == CalendarViewDelegate.WEEK_START_WITH_MON) {
            preDiff = week == 1 ? 6 : week - weekStart;
        }
        long startTime = calendar.getTimeInMillis();
        calendar.clear();
        calendar.set(java.util.Calendar.YEAR, year);
        calendar.set(java.util.Calendar.DAY_OF_MONTH, 1);
        calendar.set(java.util.Calendar.MONTH, month);
        int endActualMaximum = calendar.getActualMaximum(java.util.Calendar.DATE);
        calendar.set(java.util.Calendar.DAY_OF_MONTH, Math.min(monthStartDay, endActualMaximum) - 1);
        calendar.set(java.util.Calendar.HOUR_OF_DAY, 23);
        calendar.set(java.util.Calendar.MINUTE, 59);
        calendar.set(java.util.Calendar.SECOND, 59);
        calendar.set(java.util.Calendar.MILLISECOND, 999);
        long endTime = calendar.getTimeInMillis();
        int monthDaysCount = (int) Math.ceil((endTime - startTime) / (1000.0 * 60 * 60 * 24));
        int endWeek = calendar.get(java.util.Calendar.DAY_OF_WEEK);
        int nextDiff = endWeek == 7 ? 6 : 7 - endWeek - 1;
        if (weekStart == CalendarViewDelegate.WEEK_START_WITH_SUN) {
            nextDiff = 7 - endWeek;
        } else if (weekStart == CalendarViewDelegate.WEEK_START_WITH_MON) {
            nextDiff = endWeek == 1 ? 0 : 7 - endWeek + 1;
        }
        calendar.clear();
        calendar.setTimeInMillis(startTime);
        calendar.add(java.util.Calendar.DAY_OF_MONTH, -preDiff - 1);
        List<Calendar> items = new ArrayList<>();
        int count = delegate.getMonthViewShowMode() == CalendarViewDelegate.MODE_ALL_MONTH ? 42 : (preDiff + monthDaysCount + nextDiff);
        Calendar currentDate = delegate.getCurrentDay();
        for (int i = 0; i < count; i++) {
            calendar.add(java.util.Calendar.DAY_OF_MONTH, 1);
            Calendar calendarDate = new Calendar();
            calendarDate.setYear(calendar.get(java.util.Calendar.YEAR));
            calendarDate.setMonth(calendar.get(java.util.Calendar.MONTH) + 1);
            calendarDate.setDay(calendar.get(java.util.Calendar.DAY_OF_MONTH));
            calendarDate.setCurrentWeek(delegate.isCurrentWeek(calendar));
            if (i >= preDiff && i < preDiff + monthDaysCount) {
                calendarDate.setCurrentMonth(true);
                if (calendarDate.equals(currentDate)) {
                    calendarDate.setCurrentDay(true);
                }
            }
            setupCalendarData(calendarDate);
            items.add(calendarDate);
        }
        return items;
    }

    /**
     * 初始化日历数据
     *
     * @param calendar calendar
     */
    public static void setupCalendarData(Calendar calendar) {
        int week = CalendarUtil.getWeekFormCalendar(calendar);
        calendar.setWeekend(week == 0 || week == 6);
        calendar.setWeek(week);
        calendar.setLeapYear(CalendarUtil.isLeapYear(calendar.getYear()));
    }

    /**
     * 判断一个日期是否是周末，即周六日
     *
     * @param calendar calendar
     * @return 判断一个日期是否是周末，即周六日
     */
    public static boolean isWeekend(Calendar calendar) {
        int week = getWeekFormCalendar(calendar);
        return week == 0 || week == 6;
    }

    /**
     * 获取某个日期是星期几
     * 测试通过
     *
     * @param calendar 某个日期
     * @return 返回某个日期是星期几
     */
    public static int getWeekFormCalendar(Calendar calendar) {
        java.util.Calendar date = java.util.Calendar.getInstance();
        date.set(calendar.getYear(), calendar.getMonth() - 1, calendar.getDay());
        return date.get(java.util.Calendar.DAY_OF_WEEK) - 1;
    }

    /**
     * 是否是闰年
     *
     * @param year year
     * @return 是否是闰年
     */
    public static boolean isLeapYear(int year) {
        return ((year % 4 == 0) && (year % 100 != 0)) || (year % 400 == 0);
    }

    /**
     * 获取所在周的第一天
     *
     * @param calendar      calendar
     * @param weekStart 周起始日
     * @return 周数
     */
    public static Calendar getWeekFirstDay(Calendar calendar, int weekStart) {
        java.util.Calendar c = java.util.Calendar.getInstance();
        c.clear();
        c.set(calendar.getYear(), calendar.getMonth() - 1, calendar.getDay(), 0, 0, 0);
        c.set(java.util.Calendar.MILLISECOND, 0);
        int week = c.get(java.util.Calendar.DAY_OF_WEEK);
        int diff = week == 7 ? 0 : week;
        if (weekStart == CalendarViewDelegate.WEEK_START_WITH_SUN) {
            diff = week - 1;
        } else if (weekStart == CalendarViewDelegate.WEEK_START_WITH_MON) {
            diff = week == 1 ? 6 : week - weekStart;
        }
        c.add(java.util.Calendar.DAY_OF_MONTH, diff * -1);
        Calendar cal = new Calendar();
        cal.setYear(c.get(java.util.Calendar.YEAR));
        cal.setMonth(c.get(java.util.Calendar.MONTH) + 1);
        cal.setDay(c.get(java.util.Calendar.DAY_OF_MONTH));
        return cal;
    }

    /**
     * 获取该日期与所在周的第1天相差几天
     *
     * @param calendar  calendar
     * @param weekStart 周起始日
     * @return 相差天数
     */
    public static int getWeekFirstDayDiff(Calendar calendar, int weekStart) {
        java.util.Calendar date = java.util.Calendar.getInstance();
        date.set(calendar.getYear(), calendar.getMonth() - 1, calendar.getDay());
        int week = date.get(java.util.Calendar.DAY_OF_WEEK);
        int diff = week == 7 ? 0 : week;
        if (weekStart == CalendarViewDelegate.WEEK_START_WITH_SUN) {
            diff = week - 1;
        } else if (weekStart == CalendarViewDelegate.WEEK_START_WITH_MON) {
            diff = week == 1 ? 6 : week - weekStart;
        }
        return diff;
    }

    /**
     * 获取月视图的行数
     * Test pass
     *
     * @param year          年
     * @param month         月
     * @param monthStartDay 月起始日
     * @param weekStart     周起始日
     * @param mode          月份显示模式
     * @return 不需要多余行的高度
     */
    public static int getMonthLineCount(int year, int month, int monthStartDay, int weekStart, int mode) {
        if (mode == CalendarViewDelegate.MODE_ALL_MONTH) {
            return 6;
        }
        java.util.Calendar calendar = java.util.Calendar.getInstance();
        calendar.clear();
        calendar.set(java.util.Calendar.YEAR, year);
        calendar.set(java.util.Calendar.DAY_OF_MONTH, 1);
        calendar.set(java.util.Calendar.MONTH, month - 1);
        int actualMaximum = calendar.getActualMaximum(java.util.Calendar.DATE);
        calendar.set(java.util.Calendar.DAY_OF_MONTH, Math.min(monthStartDay, actualMaximum));
        calendar.set(java.util.Calendar.HOUR_OF_DAY, 0);
        calendar.set(java.util.Calendar.MINUTE, 0);
        calendar.set(java.util.Calendar.SECOND, 0);
        calendar.set(java.util.Calendar.MILLISECOND, 0);
        int week = calendar.get(java.util.Calendar.DAY_OF_WEEK);
        int preDiff = week == 7 ? 0 : week;
        if (weekStart == CalendarViewDelegate.WEEK_START_WITH_SUN) {
            preDiff = week - 1;
        } else if (weekStart == CalendarViewDelegate.WEEK_START_WITH_MON) {
            preDiff = week == 1 ? 6 : week - weekStart;
        }
        long startTime = calendar.getTimeInMillis();
        calendar.clear();
        calendar.set(java.util.Calendar.YEAR, year);
        calendar.set(java.util.Calendar.DAY_OF_MONTH, 1);
        calendar.set(java.util.Calendar.MONTH, month);
        int endActualMaximum = calendar.getActualMaximum(java.util.Calendar.DATE);
        calendar.set(java.util.Calendar.DAY_OF_MONTH, Math.min(monthStartDay, endActualMaximum) - 1);
        calendar.set(java.util.Calendar.HOUR_OF_DAY, 23);
        calendar.set(java.util.Calendar.MINUTE, 59);
        calendar.set(java.util.Calendar.SECOND, 59);
        calendar.set(java.util.Calendar.MILLISECOND, 999);
        long endTime = calendar.getTimeInMillis();
        int monthDaysCount = (int) Math.ceil((endTime - startTime) / (1000.0 * 60 * 60 * 24));
        int endWeek = calendar.get(java.util.Calendar.DAY_OF_WEEK);
        int nextDiff = endWeek == 7 ? 6 : 7 - endWeek - 1;
        if (weekStart == CalendarViewDelegate.WEEK_START_WITH_SUN) {
            nextDiff = 7 - endWeek;
        } else if (weekStart == CalendarViewDelegate.WEEK_START_WITH_MON) {
            nextDiff = endWeek == 1 ? 0 : 7 - endWeek + 1;
        }
        return (preDiff + monthDaysCount + nextDiff) / 7;
    }

    /**
     * 获取月视图的确切高度
     * Test pass
     *
     * @param year          年
     * @param month         月
     * @param monthStartDay 月起始日
     * @param itemHeight    每项的高度
     * @param weekStart     周起始日
     * @param mode          月份显示模式
     * @return 不需要多余行的高度
     */
    public static int getMonthViewHeight(int year, int month, int monthStartDay, int itemHeight, int weekStart, int mode) {
        return getMonthLineCount(year, month, monthStartDay, weekStart, mode) * itemHeight;
    }

    /**
     * 获取当前年月
     *
     * @param monthStartDay 月起始日
     * @return Calendar
     */
    @NonNull
    public static java.util.Calendar getCurrentCalendar(int monthStartDay) {
        java.util.Calendar calendar = java.util.Calendar.getInstance();
        calendar.clear();
        calendar.setTimeInMillis(System.currentTimeMillis());
        int actualMaximum = calendar.getActualMaximum(java.util.Calendar.DATE);
        if (actualMaximum < monthStartDay) {
            monthStartDay = actualMaximum;
        }
        int day = calendar.get(java.util.Calendar.DAY_OF_MONTH);
        if (day < monthStartDay) {
            int month = calendar.get(java.util.Calendar.MONTH);
            calendar.set(java.util.Calendar.DAY_OF_MONTH, 1);
            calendar.set(java.util.Calendar.MONTH, month - 1);
            actualMaximum = calendar.getActualMaximum(java.util.Calendar.DATE);
            calendar.set(java.util.Calendar.DAY_OF_MONTH, Math.min(day, actualMaximum));
        }
        return calendar;
    }

    /**
     * 获取当天day
     *
     * @return 【1-31】
     */
    public static int getDay() {
        java.util.Calendar calendar = java.util.Calendar.getInstance();
        calendar.clear();
        calendar.setTimeInMillis(System.currentTimeMillis());
        return calendar.get(java.util.Calendar.DAY_OF_MONTH);
    }

    /**
     * 获取选中的日期
     *
     * @param year                      年
     * @param month                     月
     * @param monthStartDay             月起始日
     * @param currentDay                当天日期
     * @param defaultCalendarSelectMode 默认选择模式
     * @param lastSelectedCalendar      上一次选择的日期
     * @return 选中的日期
     */
    public static Calendar getSelectCalendar(int year, int month, int monthStartDay, Calendar currentDay, int defaultCalendarSelectMode, Calendar lastSelectedCalendar) {
        int day = monthStartDay;
        if (defaultCalendarSelectMode == CalendarViewDelegate.LAST_MONTH_VIEW_SELECT_DAY_IGNORE_CURRENT) {
            day = lastSelectedCalendar.getDay();
        } else if (defaultCalendarSelectMode == CalendarViewDelegate.LAST_MONTH_VIEW_SELECT_DAY) {
            if (year == currentDay.getYear() && month == currentDay.getMonth()) {
                day = currentDay.getDay();
            } else {
                day = lastSelectedCalendar.getDay();
            }
        } else if (defaultCalendarSelectMode == CalendarViewDelegate.CHANGE_MONTH_SELECT_NONE) {
            if (monthStartDay <= lastSelectedCalendar.getDay()) {
                if (year == lastSelectedCalendar.getYear() && month == lastSelectedCalendar.getMonth()) {
                    return lastSelectedCalendar;
                }
            } else {
                int currentYear = year;
                int currentMonth = month + 1;
                if (currentMonth >= 13) {
                    currentYear = year + 1;
                    currentMonth = 1;
                }
                if (currentYear == lastSelectedCalendar.getYear() && currentMonth == lastSelectedCalendar.getMonth()) {
                    return lastSelectedCalendar;
                }
            }
            return null;
        }
        if (day < monthStartDay) {
            month = month + 1;
            if (month == 13) {
                year = year + 1;
                month = 1;
            }
        }
        java.util.Calendar calendar = java.util.Calendar.getInstance();
        calendar.set(java.util.Calendar.YEAR, year);
        calendar.set(java.util.Calendar.DAY_OF_MONTH, 1);
        calendar.set(java.util.Calendar.MONTH, month - 1);
        int actualMaximum = calendar.getActualMaximum(java.util.Calendar.DATE);
        day = Math.min(day, actualMaximum);
        Calendar selectedCalendar = new Calendar();
        selectedCalendar.setYear(year);
        selectedCalendar.setMonth(month);
        selectedCalendar.setDay(day);
        selectedCalendar.setCurrentMonth(true);
        selectedCalendar.setCurrentDay(selectedCalendar.equals(currentDay));
        setupCalendarData(selectedCalendar);
        return selectedCalendar;
    }

    /**
     * max 日期是否大于 min 日期
     *
     * @param minYear      最小年份
     * @param minYearMonth 最小年份对应月份
     * @param maxYear      最大月份
     * @param maxYearMonth 最大月份对应月份
     * @return true max 日期大于 min 日期，false 数据错误
     */
    static boolean isRange(int minYear, int minYearMonth, int maxYear, int maxYearMonth) {
        boolean isFit = minYearMonth >= 1 && minYearMonth <= 12 && maxYearMonth >= 1 && maxYearMonth <= 12;
        if (!isFit) {
            return false;
        }
        if (maxYear > minYear) {
            return true;
        } else if (maxYear == minYear) {
            return maxYearMonth >= minYearMonth;
        }
        return false;
    }

    /**
     * 是否在日期范围內
     * 测试通过 test pass
     *
     * @param calendar calendar
     * @param delegate CalendarViewDelegate
     * @return 是否在日期范围內
     */
    static boolean isCalendarInRange(Calendar calendar, CalendarViewDelegate delegate) {
        return isCalendarInRange(calendar, delegate.getMinYear(), delegate.getMinYearMonth(), delegate.getMaxYear(), delegate.getMaxYearMonth(), delegate.getMonthStartDay());
    }

    /**
     * 是否在日期范围內
     * 测试通过 test pass
     *
     * @param calendar      calendar
     * @param minYear       最小年份
     * @param minYearMonth  最小年份对应月份
     * @param maxYear       最大月份
     * @param maxYearMonth  最大月份对应月份
     * @param monthStartDay 月起始日
     * @return 是否在日期范围內
     */
    static boolean isCalendarInRange(Calendar calendar, int minYear, int minYearMonth, int maxYear, int maxYearMonth, int monthStartDay) {
        java.util.Calendar c = java.util.Calendar.getInstance();
        c.set(calendar.getYear(), calendar.getMonth() - 1, calendar.getDay());
        long curTime = c.getTimeInMillis();
        c.set(minYear, minYearMonth - 1, 1);
        c.set(java.util.Calendar.HOUR_OF_DAY, 0);
        c.set(java.util.Calendar.MINUTE, 0);
        c.set(java.util.Calendar.SECOND, 0);
        c.set(java.util.Calendar.MILLISECOND, 0);
        int actualMaximum = c.getActualMaximum(java.util.Calendar.DATE);
        c.set(java.util.Calendar.DAY_OF_MONTH, Math.min(monthStartDay, actualMaximum));
        long minTime = c.getTimeInMillis();
        c.set(maxYear, maxYearMonth, 1);
        actualMaximum = c.getActualMaximum(java.util.Calendar.DATE);
        c.set(java.util.Calendar.DAY_OF_MONTH, Math.min(monthStartDay, actualMaximum) - 1);
        c.set(java.util.Calendar.HOUR_OF_DAY, 23);
        c.set(java.util.Calendar.MINUTE, 59);
        c.set(java.util.Calendar.SECOND, 59);
        c.set(java.util.Calendar.MILLISECOND, 999);
        long maxTime = c.getTimeInMillis();
        return curTime >= minTime && curTime <= maxTime;
    }

    /**
     * dp转px
     *
     * @param context context
     * @param dpValue dp
     * @return px
     */
    static int dipToPx(Context context, float dpValue) {
        final float scale = context.getResources().getDisplayMetrics().density;
        return (int) (dpValue * scale + 0.5f);
    }
}
